/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package treeship.core.ship.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import treeship.core.algorithm.EICG;
import treeship.core.archetype.ArchetypeProperty;
import treeship.core.archetype.ArchetypeShip;
import treeship.core.archetype.ComponentContext;
import treeship.core.archetype.DimensionContext;
import treeship.core.archetype.PropertyContext;
import treeship.core.exceptions.CantDoThatException;
import treeship.core.service.Nexus;
import treeship.core.ship.space.Position;
import treeship.core.ship.space.Shape;
import treeship.core.ship.space.Space;
import treeship.core.ship.space.Speed;

/**
 * 
 * @author Moonchild
 */
public class TreeShip extends AbstractTreeShip<TreeShip> {

	private List<TreeShip> inners = new ArrayList<TreeShip>();
	private List<TreeShip> children = new ArrayList<TreeShip>();
	private List<TreeShip> innerForOthers = null;
	private List<TreeShip> childrenForOthers = null;
	private ArchetypeShip archetipo;
	private TreeShip parent = null;
	private Map<ArchetypeProperty, Double> props = new HashMap<ArchetypeProperty, Double>();
	private Map<ArchetypeProperty, Double> propsForOthers = null;
	private Shape shape;
	private long seed = 0;
	private Position position = null;
	private Speed speed;
	private boolean managed;

	@Override
	public ArchetypeShip getArchetype() {
		return archetipo;
	}

	private static double computeDimensionValueFor(TreeShip shp,
			DimensionContext pc) {
		double valore = pc.getDimensionAverage();
		double varianzaplus = pc.getDimensionVariant()
				* EICG.staticGetRandDouble(shp.getSeed()
						+ Nexus.getRepository().getContextData()
								.getIdDimension());
		double varianzaminus = pc.getDimensionVariant()
				* EICG.staticGetRandDouble(shp.getSeed()
						+ Nexus.getRepository().getContextData()
								.getIdDimension() + 1);
		valore = valore + varianzaplus - varianzaminus;
		return Math.max(valore, 0.0);

	}

	private static double computeFreeSpaceValueFor(TreeShip shp,
			DimensionContext pc) {
		double valore = pc.getFreeSpaceAverage();
		double varianzaplus = pc.getFreeSpaceVariant()
				* EICG.staticGetRandDouble(shp.getSeed()
						+ Nexus.getRepository().getContextData()
								.getIdFreespace());
		double varianzaminus = pc.getFreeSpaceVariant()
				* EICG.staticGetRandDouble(shp.getSeed()
						+ Nexus.getRepository().getContextData()
								.getIdFreespace() + 1);
		valore = valore + varianzaplus - varianzaminus;
		return Math.max(valore, 0.0);

	}

	private static double computeSpeedValueFor(TreeShip shp, DimensionContext pc) {
		double valore = pc.getSpeedAverage();
		double varianzaplus = pc.getSpeedVariant()
				* EICG.staticGetRandDouble(shp.getSeed()
						+ Nexus.getRepository().getContextData().getIdSpeed());
		double varianzaminus = pc.getSpeedVariant()
				* EICG.staticGetRandDouble(shp.getSeed()
						+ Nexus.getRepository().getContextData().getIdSpeed()
						+ 1);
		valore = valore + varianzaplus - varianzaminus;
		return Math.max(valore, 0.0);

	}

	private static double computePropertyValueFor(TreeShip shp,
			PropertyContext pc) {
		double valore = pc.getAverage();
		double varianzaplus = pc.getVariant()
				* EICG.staticGetRandDouble(shp.getSeed()
						+ pc.getProperty().getId());
		double varianzaminus = pc.getVariant()
				* EICG.staticGetRandDouble(shp.getSeed()
						+ pc.getProperty().getId() + 1);
		valore = valore + varianzaplus - varianzaminus;
		return Math.max(valore, pc.getMin());

	}

	private static ComponentContext tellWhichTypeIs(double randomScelta,
			List<ComponentContext> archComponents) {
		double temp = 0.0;
		for (ComponentContext figliotipo : archComponents) {
			temp += figliotipo.getWeight();
			if (temp > randomScelta) {
				return figliotipo;
			}
		}

		return archComponents.get(archComponents.size() - 1);

	}

	private void checkThereIsSpaceFor(TreeShip ship) throws CantDoThatException {
		// if (freespace < ship.getDimension()) {
		if (!ship.getShape().fitsIn(shape)) {
			CantDoThatException.cantDo("there isn't enough space to enter");
		}
	}

	public void createChildren() {
		if (archetipo.getComponents().isEmpty()) {
			return;
		}
		// old_createchildren(propsContainedOk);

		Map<ArchetypeProperty, Double> tempMap = new HashMap<ArchetypeProperty, Double>();
		for (ArchetypeProperty k : props.keySet()) {
			tempMap.put(k, props.get(k));
		}

		Space tempSize = shape.getDimension().minus(shape.getFreespace());

		List<ComponentContext> archComp = new ArrayList<ComponentContext>();
		archComp.addAll(archetipo.getComponents());
		Double w = 0.0;
		for (ComponentContext componentContext : archComp) {
			w = w + componentContext.getWeight();
		}

		while (archComp.size() > 0) {
			boolean propsContainedOk = true;
			long indice = children.size();
			double randomScelta = w * EICG.staticGetRandDouble(seed + indice);
			ComponentContext selectedType = tellWhichTypeIs(randomScelta,
					archComp);
			TreeShip chld = new TreeShip(selectedType.getComponent(),
					EICG.staticGetRand(seed + indice + 1), null);
			for (ArchetypeProperty k : chld.getProperties().keySet()) {
				if (tempMap.containsKey(k)) {
					if (tempMap.get(k) < chld.getProperties().get(k)) {
						// Esco perch� non c'� capienza
						propsContainedOk = false;
					}
				}
			}
			if (tempSize.isSmallerThan(chld.getShape().getDimension())) {
				// Esco perch� non c'� capienza
				propsContainedOk = false;
			}

			if (propsContainedOk) {
				for (ArchetypeProperty k : chld.getProperties().keySet()) {
					if (tempMap.containsKey(k)) {
						tempMap.put(k, tempMap.get(k)
								- chld.getProperties().get(k));
					}
				}
				tempSize = tempSize.minus(chld.getShape().getDimension());
				chld.createChildren();
				// Double clpos = (dimension - chld.getDimension()) *
				// EICG.staticGetRandDouble(chld.getSeed() +
				// Nexus.getRepository().getContextData().getIdPosition());
				Position clpos = Nexus.getDimensionalFactory()
						.createPositionFromSeed(
								chld.getSeed(),
								Nexus.getRepository().getContextData()
										.getIdPosition(), shape,
								chld.getShape());
				chld.setPosition(clpos);
				chld.setParent(this);
				children.add(chld);
			} else {
				archComp.remove(selectedType);
				w = w - selectedType.getWeight();
			}
		}
	}

	public long getSeed() {
		return seed;
	}

	/**
	 * The creation of a TreeShip comes in two phases: first, the "core"
	 * treeship is created (so that his parent can check if he can contain it),
	 * then, when the parent is sure that he can contain that child, he invokes
	 * createChildren on it
	 * 
	 * @param archetipo
	 * @param seed
	 */
	public TreeShip(ArchetypeShip archetipo, long seed,
			Map<String, Double> values) {
		this.seed = seed;
		this.archetipo = archetipo;

		this.shape = Nexus.getDimensionalFactory().createNewShape();
		double dimension = computeDimensionValueFor(this,
				archetipo.getDimensionContext());
		double freespace = computeFreeSpaceValueFor(this,
				archetipo.getDimensionContext());
		double speedVal = computeSpeedValueFor(this,
				archetipo.getDimensionContext());

		if (freespace > dimension) {
			freespace = dimension;
		}

		this.shape.initShape(new Space(dimension));
		this.shape.setFreespace(new Space(freespace));
		speed = new Speed(speedVal);
		if (values == null) {
			for (PropertyContext pp : archetipo.getProperties()) {
				props.put(pp.getProperty(), +computePropertyValueFor(this, pp));
			}
		} else {
			for (Entry<String, Double> entry : values.entrySet()) {
				ArchetypeProperty pp = Nexus.getRepository()
						.getArchetypePropertyFor(entry.getKey());
				props.put(pp, entry.getValue());

			}
		}
		// createChildren();
	}

	@Override
	public Map<ArchetypeProperty, Double> getProperties() {
		if (propsForOthers == null) {
			propsForOthers = Collections.unmodifiableMap(props);
		}
		return propsForOthers;
	}

	@Override
	protected void serverAcceptChild(TreeShip ship) throws CantDoThatException {
		checkIsInner(ship);

		inners.remove(ship);
		children.add(ship);
		private_addProperties(ship);
	}

	@Override
	protected void serverRemoveChild(TreeShip child) throws CantDoThatException {
		checkIsChild(child);
		children.remove(child);
		private_removeProperties(child);
		inners.add(child);
	}

	@Override
	public List<TreeShip> getChildren() {
		if (childrenForOthers == null) {
			childrenForOthers = Collections.unmodifiableList(children);
		}
		return childrenForOthers;
	}

	@Override
	public void clientClimb() throws CantDoThatException {
		checkParentExist();
		getParent().checkParentExist();
		getParent().checkIsInner(this);
		TreeShip grandParent = getParent().getParent();
		getParent().serverExit(this);
		grandParent.privateUnControlled_addInner(this);
		parent = grandParent;
	}

	@Override
	public void clientDescend(TreeShip brother) throws CantDoThatException {
		checkParentExist();
		getParent().checkIsInner(this);
		checkIsBrother(brother);
		brother.serverEnter(this);
		parent = brother;
	}

	@Override
	public List<TreeShip> getInners() {
		if (innerForOthers == null) {
			innerForOthers = Collections.unmodifiableList(inners);
		}
		return innerForOthers;
	}

	@Override
	public TreeShip getParent() {
		return parent;
	}

	protected void setParent(TreeShip par) {
		parent = par;
	}

	private void checkIfEnoughForBuild(TreeShip buildShip)
			throws CantDoThatException {
		for (ArchetypeProperty chiave : buildShip.getProperties().keySet()) {
			Double myValue = props.get(chiave);
			Double otherValue = buildShip.getProperties().get(chiave);
			if (myValue != null && myValue < otherValue) {
				CantDoThatException
						.cantDo("There isn't enough property to create it");
			}
		}
		// if (freespace < buildShip.getDimension()) {
		if (buildShip.getShape().fitsIn(shape)) {
			CantDoThatException.cantDo("Not enough freespace to create it");
		}
	}

	/*
	 * private void old_createchildren(boolean propsContainedOk) {
	 * Map<ArchetypeProperty, Double> tempMap = new HashMap<ArchetypeProperty,
	 * Double>(); for (ArchetypeProperty k : props.keySet()) { tempMap.put(k,
	 * props.get(k)); } double tempSize = dimension - freespace; while
	 * (propsContainedOk) { long indice = children.size(); double randomScelta =
	 * EICG.staticGetRandDouble(seed + indice); ArchetypeShip arche2 =
	 * tellWhichTypeIs(randomScelta, archetipo.getComponents()).getComponent();
	 * TreeShip chld = new TreeShip(arche2, EICG.staticGetRand(seed + indice +
	 * 1)); for (ArchetypeProperty k : chld.getProperties().keySet()) { if
	 * (tempMap.containsKey(k)) { if (tempMap.get(k) <
	 * chld.getProperties().get(k)) { //Esco perch� non c'� capienza
	 * propsContainedOk = false; } } } if (tempSize < chld.getDimension()) {
	 * //Esco perch� non c'� capienza propsContainedOk = false; } if
	 * (propsContainedOk) { for (ArchetypeProperty k :
	 * chld.getProperties().keySet()) { if (tempMap.containsKey(k)) {
	 * tempMap.put(k, tempMap.get(k) - chld.getProperties().get(k)); } }
	 * tempSize = tempSize - chld.getDimension(); chld.createChildren(); Double
	 * clpos = dimension * EICG.staticGetRandDouble(chld.getSeed() +
	 * Nexus.getRepository().getContextData().getIdPosition());
	 * chld.setPosition(clpos); chld.setParent(this); children.add(chld); } } }
	 */

	private void private_addProperties(TreeShip ship) {
		for (ArchetypeProperty chiave : ship.getProperties().keySet()) {
			Double shipValue = ship.getProperties().get(chiave);
			Double myValue = props.get(chiave);
			if (myValue != null) {
				props.put(chiave, myValue + shipValue);
				if (isChild()) {
					parent.pipeUpProperty(chiave, shipValue);
				}
			}
		}
	}

	protected void pipeUpProperty(ArchetypeProperty chiave, Double newVal) {
		Double valoreVecchio = props.get(chiave);
		if (valoreVecchio != null) {
			props.put(chiave, valoreVecchio + newVal);
			if (isChild()) {
				parent.pipeUpProperty(chiave, newVal);
			}
		}
	}

	/**
	 * This method allows to tell a child to add newVal to his property.
	 * Important: NEVER call this directly on 'this'!
	 * 
	 * @param chiave
	 * @param newVal
	 */
	protected void pipeDownProperty(ArchetypeProperty chiave, Double newVal) {
		Double oldValue = props.get(chiave);
		if (oldValue != null) {
			props.put(chiave, oldValue + newVal);
			for (TreeShip child : children) {
				child.pipeDownProperty(chiave, newVal
						/ child.getShape().getDimension().getValue());
			}

		}
	}

	private void private_updateProperty(ArchetypeProperty chiave) {
		Double totale = 0.0;
		for (TreeShip child : getChildren()) {
			Double valore = child.getProperties().get(chiave);
			if (valore != null) {
				totale = totale + valore;
			}
		}
		props.put(chiave, totale);
	}

	private void private_removeProperties(TreeShip ship) {
		for (ArchetypeProperty chiave : ship.getProperties().keySet()) {
			Double myValue = props.get(chiave);

			Double newVal = ship.getProperties().get(chiave);
			if (myValue != null) {
				props.put(chiave, myValue - newVal);
				if (isChild()) {
					parent.pipeUpProperty(chiave, -newVal);
				}
			}
		}
	}

	// private void private_removeProperties(TreeShip ship) {
	// for (ArchetypeProperty chiave : ship.getProperties().keySet()) {
	// Double valoreNuovo = ship.getProperties().get(chiave);
	// pipeUpProperty(chiave, -valoreNuovo);
	// }
	// }
	@Override
	protected void serverExit(TreeShip child) throws CantDoThatException {
		checkIsInner(child);
		inners.remove(child);
		shape.setFreespace(shape.getFreespace().plus(
				child.getShape().getDimension()));
		// freespace = freespace + child.getDimension();

	}

	@Override
	protected void serverEnter(TreeShip ship) throws CantDoThatException {
		checkIsBrother(ship);
		checkThereIsSpaceFor(ship);
		inners.add(ship);
		// freespace = freespace - ship.getDimension();
		shape.setFreespace(shape.getFreespace().minus(
				ship.getShape().getDimension()));
	}

	protected void privateUnControlled_addInner(TreeShip child) {
		inners.add(child);
	}

	protected void privateUnControlled_removeInner(TreeShip child) {
		inners.remove(child);
	}

	@Override
	public void spawnInner(ArchetypeShip tipoInner) throws CantDoThatException {
		if (!archetipo.getCreatives().contains(tipoInner)) {
			CantDoThatException
					.cantDo("Si � tentato di generare un prodotto non supportato");
		}
		TreeShip buildShip = Nexus.getFactory().buildShip(tipoInner.getName(),
				null, null);
		checkIfEnoughForBuild(buildShip);
		private_removeProperties(buildShip);
		inners.add(buildShip);

		shout();

	}

	private boolean isChild() {
		if (parent != null && parent.getChildren().contains(this)) {
			return true;
		}
		return false;
	}

	private void shout() {
		if (isChild()) {
			parent.shout();
		}

	}

	@Override
	public String getSelfDescription() {
		StringBuffer sb = new StringBuffer();
		sb.append(archetipo.getName() + '\n');
		if (position != null) {
			sb.append("Position:" + '\n' + position.toString() + '\n');
		}
		sb.append("Dimension " + shape.getDimension() + '\n');
		sb.append("Freespace " + shape.getFreespace() + '\n');
		sb.append("Speed " + speed + '\n');
		for (ArchetypeProperty chiave : props.keySet()) {
			sb.append(chiave.getName() + ": "
					+ Math.round(props.get(chiave) * 100) / 100.0 + '\n');
		}
		return sb.toString();
	}

	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append(archetipo.getName() + '\n');
		// for (String chiave : props.keySet()) {
		// sb.append(chiave + ": " + Math.round(props.get(chiave) * 100) / 100 +
		// '\n');
		// }
		return sb.toString();
	}

	@Override
	public Position getPosition() {
		return position;
	}

	public void setPosition(Position position) {
		this.position = position;
	}

	@Override
	public Double getOtherProperty(String s) {
		double valore = EICG.staticGetRandDouble(this.getSeed() + s.hashCode());
		return valore;

	}

	public double drainProperty(ArchetypeProperty ap, double amount) {
		double howmuchIcanGive = Math.min(props.get(ap), amount);
		if (howmuchIcanGive == 0.0) {
			return 0.0;
		}
		props.put(ap, props.get(ap) - howmuchIcanGive);
		if (isChild()) {
			parent.pipeUpProperty(ap, -howmuchIcanGive);
		}
		for (TreeShip child : children) {
			child.pipeDownProperty(ap, -howmuchIcanGive);
		}

		return howmuchIcanGive;

	}

	public void fillProperty(ArchetypeProperty ap, double amount) {
		props.put(ap, props.get(ap) + amount);
		if (isChild()) {
			parent.pipeUpProperty(ap, amount);
		}
		for (TreeShip child : children) {
			child.pipeDownProperty(ap, amount);
		}
	}

	@Override
	public Shape getShape() {
		return shape;
	}

	@Override
	public void goTo(Position p) throws CantDoThatException {
		if (getParent() == null) {
			CantDoThatException.cantDo("parent null");
		}
		if (!getParent().getShape().contains(p)) {
			CantDoThatException
					.cantDo("trying to move to a position outside the parent");
		}
		Position newPos = p.goFromPositionWithSpeed(position, speed);
		Position adjusted = parent.getShape().adjustPositionForInnerShape(
				newPos, shape);
		setPosition(adjusted);
	}

	@Override
	public Speed getSpeed() {
		return speed;
	}

}
